/*-------------------------------------------------------------------------
 *
 * snapmgr.c
 *		PostgreSQL 快照管理器
 *
 * 我们通过两种方式跟踪快照：那些由 resowner.c “注册”的，以及“活动快照”栈。
 * 两者中的所有快照都生活在持久内存中。当一个快照不再在这些列表中的任何一个中（通过
 * 每个快照上的单独 refcounts 进行跟踪），其内存可以被释放。
 *
 * FirstXactSnapshot（如果有）会被特别对待：我们增加其 regd_count 并将其列入 RegisteredSnapshots，
 * 但此引用不会被资源所有者跟踪。我们曾经使用 TopTransactionResourceOwner 来跟踪这个
 * 快照引用，但这会引入逻辑循环，因此无法以理智的方式清理。更好的方法是将此引用视为
 * 内部跟踪的注册，以便这个模块完全低于 ResourceOwners。
 *
 * 同样，任何通过 pg_export_snapshot 导出的快照都有 regd_count = 1，并列在 RegisteredSnapshots 中，
 * 但不受任何资源所有者的跟踪。
 *
 * 同样地，CatalogSnapshot 在有效时列在 RegisteredSnapshots 中，但不受任何资源所有者的跟踪。
 *
 * 对于在逻辑解码过程中使用的历史快照也是如此，它们的生命周期被单独管理（因为它们的生命周期
 * 超过了一个 xact.c 事务）。
 *
 * 这些安排使我们能够在没有此事务引用的快照时重置 MyProc->xmin，并在最旧的 Xmin 不再
 * 被引用时将其推进。然而，为了简单起见，只有注册快照（而不是活动快照）参与跟踪哪个
 * 是最旧的；我们不会尝试在活动快照栈为空时更改 MyProc->xmin。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/utils/time/snapmgr.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <sys/stat.h>
#include <unistd.h>

#include "access/subtrans.h"
#include "access/transam.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "datatype/timestamp.h"
#include "lib/pairingheap.h"
#include "miscadmin.h"
#include "storage/predicate.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinval.h"
#include "storage/sinvaladt.h"
#include "storage/spin.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/old_snapshot.h"
#include "utils/rel.h"
#include "utils/resowner_private.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"


/*
 * GUC参数
 */
int			old_snapshot_threshold; /* 以分钟为单位，-1 禁用 */

volatile OldSnapshotControlData *oldSnapshotControl;


/*
 * CurrentSnapshot 指向在事务快照模式下拍摄的唯一快照，以及在读取提交事务中拍摄的最新快照。
 * SecondarySnapshot 是一个快照，它在当前瞬间始终是最新的，即使在事务快照模式下。
 * 它只应被用于特殊用途代码（例如，RI 检查）。CatalogSnapshot 指向一个 MVCC 快照，旨在用于目录扫描；
 * 在系统目录发生更改时，我们必须使其无效。
 *
 * 这些 SnapshotData 结构是静态的，以简化内存分配
 * （请参见 GetSnapshotData 中的黑客以避免重复的 malloc/free）。
 */
static SnapshotData CurrentSnapshotData = {SNAPSHOT_MVCC};
static SnapshotData SecondarySnapshotData = {SNAPSHOT_MVCC};
SnapshotData CatalogSnapshotData = {SNAPSHOT_MVCC};
SnapshotData SnapshotSelfData = {SNAPSHOT_SELF};
SnapshotData SnapshotAnyData = {SNAPSHOT_ANY};

/* 指向有效快照的指针 */
static Snapshot CurrentSnapshot = NULL;
static Snapshot SecondarySnapshot = NULL;
static Snapshot CatalogSnapshot = NULL;
static Snapshot HistoricSnapshot = NULL;

/*
 * 这些通过 GetSnapshotData 更新。我们以这种方式初始化它们
 * 方便 TransactionIdIsInProgress：即使在引导模式下，我们也不希望它说 BootstrapTransactionId 正在进行中。
 */
TransactionId TransactionXmin = FirstNormalTransactionId;
TransactionId RecentXmin = FirstNormalTransactionId;

/* (table, ctid) => (cmin, cmax) 在时光旅行中的映射 */
static HTAB *tuplecid_data = NULL;

/*
 * 活动快照堆栈的元素。
 *
 * 这里的每个元素都恰好占据 SnapshotData 上的一个 active_count。
 *
 * 注意：代码假设此列表中的元素按 as_level 的非递增顺序排列；
 * 此外，该列表必须以 NULL 结尾。
 */
typedef struct ActiveSnapshotElt
{
	Snapshot	as_snap;
	int			as_level;
	struct ActiveSnapshotElt *as_next;
} ActiveSnapshotElt;

/* 活动快照的堆栈顶部 */
static ActiveSnapshotElt *ActiveSnapshot = NULL;

/* 活动快照的堆栈底部 */
static ActiveSnapshotElt *OldestActiveSnapshot = NULL;

/*
 * 当前注册的快照。按 xmin 在堆中排序，以便我们可以快速找到具有最低 xmin 的一个，从而推进 MyProc->xmin。
 */
static int	fc_xmin_cmp(const pairingheap_node *fc_a, const pairingheap_node *fc_b,
					 void *fc_arg);

static pairingheap RegisteredSnapshots = {&fc_xmin_cmp, NULL, NULL};

/* 在事务中第一次调用 GetTransactionSnapshot？ */
bool		FirstSnapshotSet = false;

/*
 * 记住可序列化事务快照（如果有的话）。我们不能信任 FirstSnapshotSet 与 IsolationUsesXactSnapshot() 的组合，因为
 * GUC 可能在我们之前被重置，从而更改 IsolationUsesXactSnapshot 的值。
 */
static Snapshot FirstXactSnapshot = NULL;

/* 定义导出快照文件的路径名 */
#define SNAPSHOT_EXPORT_DIR "pg_snapshots"

/* 存储有关导出快照信息的结构。 */
typedef struct ExportedSnapshot
{
	char	   *snapfile;
	Snapshot	snapshot;
} ExportedSnapshot;

/* 当前事务的导出快照（ExportedSnapshot 结构的列表） */
static List *exportedSnapshots = NIL;

/* 本地函数的原型 */
static TimestampTz fc_AlignTimestampToMinuteBoundary(TimestampTz fc_ts);
static Snapshot fc_CopySnapshot(Snapshot fc_snapshot);
static void fc_FreeSnapshot(Snapshot fc_snapshot);
static void fc_SnapshotResetXmin(void);

/*
 * 快照字段将被序列化。
 *
 * 仅这些字段需要发送到协作的后端；其余字段可以（并且必须）在恢复时由接收方设置。
 */
typedef struct SerializedSnapshotData
{
	TransactionId xmin;
	TransactionId xmax;
	uint32		xcnt;
	int32		subxcnt;
	bool		suboverflowed;
	bool		takenDuringRecovery;
	CommandId	curcid;
	TimestampTz whenTaken;
	XLogRecPtr	lsn;
} SerializedSnapshotData;

Size SnapMgrShmemSize(void)
{
	Size		fc_size;

	fc_size = offsetof(OldSnapshotControlData, xid_by_minute);
	if (old_snapshot_threshold > 0)
		fc_size = add_size(fc_size, mul_size(sizeof(TransactionId),
									   OLD_SNAPSHOT_TIME_MAP_ENTRIES));

	return fc_size;
}

/*
 * 初始化以管理旧快照检测。
 */
void SnapMgrInit(void)
{
	bool		fc_found;

	/*
	 * 创建或附加到 OldSnapshotControlData 结构。
	 */
	oldSnapshotControl = (volatile OldSnapshotControlData *)
		ShmemInitStruct("OldSnapshotControlData",
						SnapMgrShmemSize(), &fc_found);

	if (!fc_found)
	{
		SpinLockInit(&oldSnapshotControl->mutex_current);
		oldSnapshotControl->current_timestamp = 0;
		SpinLockInit(&oldSnapshotControl->mutex_latest_xmin);
		oldSnapshotControl->latest_xmin = InvalidTransactionId;
		oldSnapshotControl->next_map_update = 0;
		SpinLockInit(&oldSnapshotControl->mutex_threshold);
		oldSnapshotControl->threshold_timestamp = 0;
		oldSnapshotControl->threshold_xid = InvalidTransactionId;
		oldSnapshotControl->head_offset = 0;
		oldSnapshotControl->head_timestamp = 0;
		oldSnapshotControl->count_used = 0;
	}
}

/*
 * GetTransactionSnapshot
 *		获取事务中新查询的适当快照。
 *
 * 请注意，返回值可能指向将被未来调用和 CommandCounterIncrement() 修改的静态存储。
 * 调用者应该在返回的快照上调用 RegisterSnapshot 或 PushActiveSnapshot，如果它要使用很长时间。
 */
Snapshot GetTransactionSnapshot(void)
{
	/*
	 * 如果正在进行逻辑解码，则返回历史快照。在这个（子）事务中，我们永远不需要非历史事务快照，
	 * 因此不需要小心为后续调用设置一个。
	 */
	if (HistoricSnapshotActive())
	{
		Assert(!FirstSnapshotSet);
		return HistoricSnapshot;
	}

	/* 事务中的第一次调用？ */
	if (!FirstSnapshotSet)
	{
		/*
		 * 不允许目录快照比事务快照更旧。 必须首先这样做以允许空堆 Assert 成功。
		 */
		InvalidateCatalogSnapshot();

		Assert(pairingheap_is_empty(&RegisteredSnapshots));
		Assert(FirstXactSnapshot == NULL);

		if (IsInParallelMode())
			elog(ERROR,
				 "cannot take query snapshot during a parallel operation");

		/*
		 * 在事务快照模式下，第一个快照必须保持直到事务结束，无论调用者对此做什么，
		 * 因此我们必须复制它，而不是直接返回 CurrentSnapshotData。 
		 * 此外，如果我们正在以可序列化模式运行，predicate.c 需要将快照获取包装在自己的处理之中。
		 */
		if (IsolationUsesXactSnapshot())
		{
			/* 首先，在 CurrentSnapshotData 中创建快照 */
			if (IsolationIsSerializable())
				CurrentSnapshot = GetSerializableTransactionSnapshot(&CurrentSnapshotData);
			else
				CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
			/* 制作保存的副本 */
			CurrentSnapshot = fc_CopySnapshot(CurrentSnapshot);
			FirstXactSnapshot = CurrentSnapshot;
			/* 在 FirstXactSnapshot 中标记为“已注册” */
			FirstXactSnapshot->regd_count++;
			pairingheap_add(&RegisteredSnapshots, &FirstXactSnapshot->ph_node);
		}
		else
			CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);

		FirstSnapshotSet = true;
		return CurrentSnapshot;
	}

	if (IsolationUsesXactSnapshot())
		return CurrentSnapshot;

	/* 不允许目录快照比事务快照更旧。 */
	InvalidateCatalogSnapshot();

	CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);

	return CurrentSnapshot;
}

/*
 * GetLatestSnapshot
 *		获取一个截至当前时刻的最新快照，
 *		即使我们正在执行事务快照模式。
 */
Snapshot GetLatestSnapshot(void)
{
	/*
	 * 我们可能能放宽这一点，但不会有其他任何能正常工作的需要它的情况。
	 */
	if (IsInParallelMode())
		elog(ERROR,
			 "cannot update SecondarySnapshot during a parallel operation");

	/*
	 * 到目前为止，没有任何要求在逻辑解码期间支持 GetLatestSnapshot() 的情况，
	 * 但如果需要，添加这个功能并不困难。
	 */
	Assert(!HistoricSnapshotActive());

	/* 如果是事务中的第一次调用，继续设置事务快照 */
	if (!FirstSnapshotSet)
		return GetTransactionSnapshot();

	SecondarySnapshot = GetSnapshotData(&SecondarySnapshotData);

	return SecondarySnapshot;
}

/*
 * GetOldestSnapshot
 *
 *		获取交易中已知的最旧快照，以 LSN 为依据。
 *		如果没有活动或注册的快照，将返回 NULL。
 */
Snapshot GetOldestSnapshot(void)
{
	Snapshot	fc_OldestRegisteredSnapshot = NULL;
	XLogRecPtr	fc_RegisteredLSN = InvalidXLogRecPtr;

	if (!pairingheap_is_empty(&RegisteredSnapshots))
	{
		fc_OldestRegisteredSnapshot = pairingheap_container(SnapshotData, ph_node,
														 pairingheap_first(&RegisteredSnapshots));
		fc_RegisteredLSN = fc_OldestRegisteredSnapshot->lsn;
	}

	if (OldestActiveSnapshot != NULL)
	{
		XLogRecPtr	fc_ActiveLSN = OldestActiveSnapshot->as_snap->lsn;

		if (XLogRecPtrIsInvalid(fc_RegisteredLSN) || fc_RegisteredLSN > fc_ActiveLSN)
			return OldestActiveSnapshot->as_snap;
	}

	return fc_OldestRegisteredSnapshot;
}

/*
 * GetCatalogSnapshot
 *		获取一个足够最新的快照，以便扫描具有指定 OID 的系统目录。
 */
Snapshot GetCatalogSnapshot(Oid fc_relid)
{
	/*
	 * 在我们进行逻辑解码时返回历史快照，以便我们可以
	 * 查看目录的适当状态。
	 *
	 * 这是在完成解码后需要重置系统缓存的主要原因。
	 */
	if (HistoricSnapshotActive())
		return HistoricSnapshot;

	return GetNonHistoricCatalogSnapshot(fc_relid);
}

/*
 * GetNonHistoricCatalogSnapshot
 *		获取一个足够最新的快照，以便即使在设置了历史快照的情况下，
 *		也能扫描具有指定 OID 的系统目录。
 */
Snapshot GetNonHistoricCatalogSnapshot(Oid fc_relid)
{
	/*
	 * 如果调用者试图扫描没有 syscache 的关系，
	 * 则在更新时不会发送 catcache 无效化。对于一些关键关系，
	 * 会发送快照无效化。 如果我们试图扫描一个既不发送 catcache
	 * 也不发送快照无效化的关系，我们必须每次刷新快照。
	 */
	if (CatalogSnapshot &&
		!RelationInvalidatesSnapshotsOnly(fc_relid) &&
		!RelationHasSysCache(fc_relid))
		InvalidateCatalogSnapshot();

	if (CatalogSnapshot == NULL)
	{
		/* 获取新快照。 */
		CatalogSnapshot = GetSnapshotData(&CatalogSnapshotData);

		/*
		 * 确保目录快照在关于推进 PGPROC->xmin 的决策中被考虑。
		 * 我们可以应用 RegisterSnapshot，但那会导致制作物理副本， 
		 * 这是多余的； 并且它还会创建对某些资源所有者的依赖关系，
		 * 我们由于在本文件开头解释的原因不想要。 而是
		 * 直接将 CatalogSnapshot 放入配对堆中。这当然必须在 InvalidateCatalogSnapshot 中反转。
		 *
		 * 注意：最好让它不可能抛出错误，因为 CatalogSnapshot 指针已经有效。
		 */
		pairingheap_add(&RegisteredSnapshots, &CatalogSnapshot->ph_node);
	}

	return CatalogSnapshot;
}

/*
 * InvalidateCatalogSnapshot
 *		将当前目录快照（如果有的话）标记为无效
 *
 * 我们可以更改这个 API 以允许调用者提供更精细的无效化详细信息，
 * 这样关系 A 的更改就不会阻止我们使用缓存的快照扫描关系 B，
 * 但迄今为止没有证据表明我们追踪这类细节所花费的 CPU 周期会得到很好的利用。
 */
void InvalidateCatalogSnapshot(void)
{
	if (CatalogSnapshot)
	{
		pairingheap_remove(&RegisteredSnapshots, &CatalogSnapshot->ph_node);
		CatalogSnapshot = NULL;
		fc_SnapshotResetXmin();
	}
}

/*
 * InvalidateCatalogSnapshotConditionally
 *		如果这是我们唯一的目录快照，则删除它
 *
 * 当我们即将等待客户端输入时，会调用此函数，因此我们不想继续持有目录快照，
 * 如果这可能意味着全球 xmin 地平线无法推进。 然而，如果还有其他快照仍然活动或注册，
 * 目录快照可能不是最旧的，因此我们可以保留它。
 */
void InvalidateCatalogSnapshotConditionally(void)
{
	if (CatalogSnapshot &&
		ActiveSnapshot == NULL &&
		pairingheap_is_singular(&RegisteredSnapshots))
		InvalidateCatalogSnapshot();
}


/*
 * SnapshotSetCommandId
 *		将 CommandCounterIncrement 传播到静态快照中（如果已设置）
 */
void SnapshotSetCommandId(CommandId fc_curcid)
{
	if (!FirstSnapshotSet)
		return;

	if (CurrentSnapshot)
		CurrentSnapshot->curcid = fc_curcid;
	if (SecondarySnapshot)
		SecondarySnapshot->curcid = fc_curcid;
	/* 我们是否也应该对 CatalogSnapshot 做同样的事情？ */
}

/*
 * SetTransactionSnapshot
 *		从导入的 MVCC 快照中设置事务的快照。
 *
 * 注意，这与 GetTransactionSnapshot 非常紧密相关 --- 必须考虑 GetTransactionSnapshot 中初次快照案例的所有相同因素。
 */
static void fc_SetTransactionSnapshot(Snapshot fc_sourcesnap, VirtualTransactionId *fc_sourcevxid,
					   int fc_sourcepid, PGPROC *fc_sourceproc)
{
	/* 调用者应该已经检查过这一点 */
	Assert(!FirstSnapshotSet);

	/* 更好这样做，以确保后续的 Assert 成功。 */
	InvalidateCatalogSnapshot();

	Assert(pairingheap_is_empty(&RegisteredSnapshots));
	Assert(FirstXactSnapshot == NULL);
	Assert(!HistoricSnapshotActive());

	/*
	 * 尽管我们不会使用它计算的快照，但我们必须
	 * 调用 GetSnapshotData，原因有两个：(1) 确保
	 * CurrentSnapshotData 的 XID 数组已分配，以及 (2) 更新
	 * GlobalVis* 的状态。
	 */
	CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);

	/*
	 * 现在从源快照复制适当的字段。
	 */
	CurrentSnapshot->xmin = fc_sourcesnap->xmin;
	CurrentSnapshot->xmax = fc_sourcesnap->xmax;
	CurrentSnapshot->xcnt = fc_sourcesnap->xcnt;
	Assert(fc_sourcesnap->xcnt <= GetMaxSnapshotXidCount());
	if (fc_sourcesnap->xcnt > 0)
		memcpy(CurrentSnapshot->xip, fc_sourcesnap->xip,
			   fc_sourcesnap->xcnt * sizeof(TransactionId));
	CurrentSnapshot->subxcnt = fc_sourcesnap->subxcnt;
	Assert(fc_sourcesnap->subxcnt <= GetMaxSnapshotSubxidCount());
	if (fc_sourcesnap->subxcnt > 0)
		memcpy(CurrentSnapshot->subxip, fc_sourcesnap->subxip,
			   fc_sourcesnap->subxcnt * sizeof(TransactionId));
	CurrentSnapshot->suboverflowed = fc_sourcesnap->suboverflowed;
	CurrentSnapshot->takenDuringRecovery = fc_sourcesnap->takenDuringRecovery;
	/* 注意：curcid 不应该被复制，这只是一个地方性问题 */

	CurrentSnapshot->snapXactCompletionCount = 0;

	/*
	 * 现在我们必须修复 GetSnapshotData 对 MyProc->xmin 和
	 * TransactionXmin 的处理。这里有一个竞争条件：为了确保我们不会
	 * 使全局 xmin 回退，我们必须检查源事务是否仍在运行，而这必须
	 * 原子地完成。因此让 procarray.c 来处理它。
	 *
	 * 注意：在可串行化模式下，predicate.c 会第二次执行此操作。这里似乎没有必要扭曲逻辑以避免两次调用，
	 * 特别是因为不清楚 predicate.c 是否 *必须* 这样做。
	 */
	if (fc_sourceproc != NULL)
	{
		if (!ProcArrayInstallRestoredXmin(CurrentSnapshot->xmin, fc_sourceproc))
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("could not import the requested snapshot"),
					 errdetail("The source transaction is not running anymore.")));
	}
	else if (!ProcArrayInstallImportedXmin(CurrentSnapshot->xmin, fc_sourcevxid))
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("could not import the requested snapshot"),
				 errdetail("The source process with PID %d is not running anymore.",
						   fc_sourcepid)));

	/*
	 * 在事务快照模式下，第一个快照必须持续到事务结束，
	 * 所以我们必须对此进行复制。此外，如果我们在可串行化模式下运行，
	 * predicate.c 需要执行自己的处理。
	 */
	if (IsolationUsesXactSnapshot())
	{
		if (IsolationIsSerializable())
			SetSerializableTransactionSnapshot(CurrentSnapshot, fc_sourcevxid,
											   fc_sourcepid);
		/* 制作保存的副本 */
		CurrentSnapshot = fc_CopySnapshot(CurrentSnapshot);
		FirstXactSnapshot = CurrentSnapshot;
		/* 在 FirstXactSnapshot 中标记为“已注册” */
		FirstXactSnapshot->regd_count++;
		pairingheap_add(&RegisteredSnapshots, &FirstXactSnapshot->ph_node);
	}

	FirstSnapshotSet = true;
}

/*
 * CopySnapshot
 *		复制给定的快照。
 *
 * 复制是在 TopTransactionContext 中使用 palloc 分配的，初始引用计数设置为 0。
 * 返回的快照被设置为已复制标志。
 */
static Snapshot fc_CopySnapshot(Snapshot fc_snapshot)
{
	Snapshot	fc_newsnap;
	Size		fc_subxipoff;
	Size		fc_size;

	Assert(fc_snapshot != InvalidSnapshot);

	/* 我们在同一个 palloc 块中分配所需的任何 XID 数组。 */
	fc_size = fc_subxipoff = sizeof(SnapshotData) +
		fc_snapshot->xcnt * sizeof(TransactionId);
	if (fc_snapshot->subxcnt > 0)
		fc_size += fc_snapshot->subxcnt * sizeof(TransactionId);

	fc_newsnap = (Snapshot) MemoryContextAlloc(TopTransactionContext, fc_size);
	memcpy(fc_newsnap, fc_snapshot, sizeof(SnapshotData));

	fc_newsnap->regd_count = 0;
	fc_newsnap->active_count = 0;
	fc_newsnap->copied = true;
	fc_newsnap->snapXactCompletionCount = 0;

	/* 设置 XID 数组 */
	if (fc_snapshot->xcnt > 0)
	{
		fc_newsnap->xip = (TransactionId *) (fc_newsnap + 1);
		memcpy(fc_newsnap->xip, fc_snapshot->xip,
			   fc_snapshot->xcnt * sizeof(TransactionId));
	}
	else
		fc_newsnap->xip = NULL;

	/*
	 * 设置 subXID 数组。如果它溢出，就不必复制它，
	 * 因为在这种情况下它不会在任何地方使用。除了如果这是在恢复期间拍摄的快照；
	 * 在这种情况下，所有顶级 XID 也在 subxip 中，所以我们不能丢失它们。
	 */
	if (fc_snapshot->subxcnt > 0 &&
		(!fc_snapshot->suboverflowed || fc_snapshot->takenDuringRecovery))
	{
		fc_newsnap->subxip = (TransactionId *) ((char *) fc_newsnap + fc_subxipoff);
		memcpy(fc_newsnap->subxip, fc_snapshot->subxip,
			   fc_snapshot->subxcnt * sizeof(TransactionId));
	}
	else
		fc_newsnap->subxip = NULL;

	return fc_newsnap;
}

/*
 * FreeSnapshot
 *		释放与快照相关的内存。
 */
static void fc_FreeSnapshot(Snapshot fc_snapshot)
{
	Assert(fc_snapshot->regd_count == 0);
	Assert(fc_snapshot->active_count == 0);
	Assert(fc_snapshot->copied);

	pfree(fc_snapshot);
}

/*
 * PushActiveSnapshot
 *		将给定快照设置为当前活动快照。
 *
 * 如果传递的快照是静态分配的，或者可能会受到未来命令计数器更新的影响，
 * 则创建一个新长寿命副本，活跃引用计数=1。否则，只需增加引用计数。
 */
void PushActiveSnapshot(Snapshot fc_snap)
{
	PushActiveSnapshotWithLevel(fc_snap, GetCurrentTransactionNestLevel());
}

/*
 * PushActiveSnapshotWithLevel
 *		将给定快照设置为当前活动快照。
 *
 * 与 PushActiveSnapshot 相同，除了调用者可以指定“拥有”该快照的
 * 事务嵌套级别。这个级别不能比快照栈的当前顶部更深。
 */
void PushActiveSnapshotWithLevel(Snapshot fc_snap, int fc_snap_level)
{
	ActiveSnapshotElt *fc_newactive;

	Assert(fc_snap != InvalidSnapshot);
	Assert(ActiveSnapshot == NULL || fc_snap_level >= ActiveSnapshot->as_level);

	fc_newactive = MemoryContextAlloc(TopTransactionContext, sizeof(ActiveSnapshotElt));

	/*
	 * 在这里检查 SecondarySnapshot 可能是无用的，但确保一下似乎更好。
	 */
	if (fc_snap == CurrentSnapshot || fc_snap == SecondarySnapshot || !fc_snap->copied)
		fc_newactive->as_snap = fc_CopySnapshot(fc_snap);
	else
		fc_newactive->as_snap = fc_snap;

	fc_newactive->as_next = ActiveSnapshot;
	fc_newactive->as_level = fc_snap_level;

	fc_newactive->as_snap->active_count++;

	ActiveSnapshot = fc_newactive;
	if (OldestActiveSnapshot == NULL)
		OldestActiveSnapshot = ActiveSnapshot;
}

/*
 * PushCopiedSnapshot
 *		与上面相同，除了强制复制所呈现的快照。
 *
 * 当 ActiveSnapshot 必须是可修改的时，应使用此方法，
 * 例如，如果调用者打算调用 UpdateActiveSnapshotCommandId。
 * 新快照将在从堆栈中弹出时释放。
 */
void PushCopiedSnapshot(Snapshot fc_snapshot)
{
	PushActiveSnapshot(fc_CopySnapshot(fc_snapshot));
}

/*
 * UpdateActiveSnapshotCommandId
 *
 * 更新活动快照的当前 CID。这仅适用于未在其他地方引用的快照。
 */
void UpdateActiveSnapshotCommandId(void)
{
	CommandId	fc_save_curcid,
				fc_curcid;

	Assert(ActiveSnapshot != NULL);
	Assert(ActiveSnapshot->as_snap->active_count == 1);
	Assert(ActiveSnapshot->as_snap->regd_count == 0);

	/*
	 * 不允许在并行操作期间修改活动快照。
	 * 我们在并行操作开始时与工作后端共享快照，因此对快照的任何更改可能导致
	 * 不一致。我们有其他防御措施来防止
	 * CommandCounterIncrement，但有几个地方直接调用此操作，因此我们在这里设置了额外的保护。
	 */
	fc_save_curcid = ActiveSnapshot->as_snap->curcid;
	fc_curcid = GetCurrentCommandId(false);
	if (IsInParallelMode() && fc_save_curcid != fc_curcid)
		elog(ERROR, "cannot modify commandid in active snapshot during a parallel operation");
	ActiveSnapshot->as_snap->curcid = fc_curcid;
}

/*
 * PopActiveSnapshot
 *
 * 从活动快照堆栈中移除顶部快照，减少引用计数，
 * 如果这是最后一个引用，则释放它。
 */
void PopActiveSnapshot(void)
{
	ActiveSnapshotElt *fc_newstack;

	fc_newstack = ActiveSnapshot->as_next;

	Assert(ActiveSnapshot->as_snap->active_count > 0);

	ActiveSnapshot->as_snap->active_count--;

	if (ActiveSnapshot->as_snap->active_count == 0 &&
		ActiveSnapshot->as_snap->regd_count == 0)
		fc_FreeSnapshot(ActiveSnapshot->as_snap);

	pfree(ActiveSnapshot);
	ActiveSnapshot = fc_newstack;
	if (ActiveSnapshot == NULL)
		OldestActiveSnapshot = NULL;

	fc_SnapshotResetXmin();
}

/*
 * GetActiveSnapshot
 *		返回活动堆栈中最顶部的快照。
 */
Snapshot GetActiveSnapshot(void)
{
	Assert(ActiveSnapshot != NULL);

	return ActiveSnapshot->as_snap;
}

/*
 * ActiveSnapshotSet
 *		返回活动堆栈中是否至少有一个快照。
 */
bool ActiveSnapshotSet(void)
{
	return ActiveSnapshot != NULL;
}

/*
 * RegisterSnapshot
 *		将快照注册为当前资源所有者正在使用中。
 *
 * 如果传递 InvalidSnapshot，则不注册它。
 */
Snapshot RegisterSnapshot(Snapshot fc_snapshot)
{
	if (fc_snapshot == InvalidSnapshot)
		return InvalidSnapshot;

	return RegisterSnapshotOnOwner(fc_snapshot, CurrentResourceOwner);
}

/*
 * RegisterSnapshotOnOwner
 *		与上面相同，但使用指定的资源所有者。
 */
Snapshot RegisterSnapshotOnOwner(Snapshot fc_snapshot, ResourceOwner fc_owner)
{
	Snapshot	fc_snap;

	if (fc_snapshot == InvalidSnapshot)
		return InvalidSnapshot;

	/* 静态快照？创建一个持久副本 */
	fc_snap = fc_snapshot->copied ? fc_snapshot : fc_CopySnapshot(fc_snapshot);

	/* 并告知 resowner.c。 */
	ResourceOwnerEnlargeSnapshots(fc_owner);
	fc_snap->regd_count++;
	ResourceOwnerRememberSnapshot(fc_owner, fc_snap);

	if (fc_snap->regd_count == 1)
		pairingheap_add(&RegisteredSnapshots, &fc_snap->ph_node);

	return fc_snap;
}

/*
 * UnregisterSnapshot
 *
 * 减少快照的引用计数，从 CurrentResourceOwner 中删除相应的引用，
 * 如果没有其他引用，则释放该快照。
 */
void UnregisterSnapshot(Snapshot fc_snapshot)
{
	if (fc_snapshot == NULL)
		return;

	UnregisterSnapshotFromOwner(fc_snapshot, CurrentResourceOwner);
}

/*
 * UnregisterSnapshotFromOwner
 *		与上面相同，但使用指定的资源所有者。
 */
void UnregisterSnapshotFromOwner(Snapshot fc_snapshot, ResourceOwner fc_owner)
{
	if (fc_snapshot == NULL)
		return;

	Assert(fc_snapshot->regd_count > 0);
	Assert(!pairingheap_is_empty(&RegisteredSnapshots));

	ResourceOwnerForgetSnapshot(fc_owner, fc_snapshot);

	fc_snapshot->regd_count--;
	if (fc_snapshot->regd_count == 0)
		pairingheap_remove(&RegisteredSnapshots, &fc_snapshot->ph_node);

	if (fc_snapshot->regd_count == 0 && fc_snapshot->active_count == 0)
	{
		fc_FreeSnapshot(fc_snapshot);
		fc_SnapshotResetXmin();
	}
}

/*
 * 比较函数用于 RegisteredSnapshots 堆。快照按 xmin 排序，
 * 所以具有最小 xmin 的快照位于顶部。
 */
static int fc_xmin_cmp(const pairingheap_node *fc_a, const pairingheap_node *fc_b, void *fc_arg)
{
	const SnapshotData *fc_asnap = pairingheap_const_container(SnapshotData, ph_node, fc_a);
	const SnapshotData *fc_bsnap = pairingheap_const_container(SnapshotData, ph_node, fc_b);

	if (TransactionIdPrecedes(fc_asnap->xmin, fc_bsnap->xmin))
		return 1;
	else if (TransactionIdFollows(fc_asnap->xmin, fc_bsnap->xmin))
		return -1;
	else
		return 0;
}


/*
 * SnapshotResetXmin
 *
 * 如果没有更多快照，我们可以将我们的 PGPROC->xmin 重置为
 * InvalidTransactionId。请注意，我们可以在不加锁的情况下执行此操作，因为我们假设
 * 存储一个 Xid 是原子的。
 *
 * 即使还有一些剩余的快照，我们也可能能够在某种程度上推进我们的
 * PGPROC->xmin。这通常发生在一个门户被删除时。为了提高效率，我们仅在
 * 活动快照堆栈为空时考虑重新计算 PGPROC->xmin；这使我们不需要跟踪
 * 哪个活动快照是最旧的。
 *
 * 注意：在这里使用 GetOldestSnapshot() 是很诱人的，以便我们可以在计算中包含
 * 活动快照。然而，那是通过 LSN 而不是 xmin 进行比较，所以它并不完全明确
 * 这是否是同一回事。此外，我们将严重依赖于假设底层活动快照
 * 堆栈条目具有最旧的 xmin。（当前对 GetOldestSnapshot() 的使用
 * 实际上并不至关重要，但这会是。）
 */
static void fc_SnapshotResetXmin(void)
{
	Snapshot	fc_minSnapshot;

	if (ActiveSnapshot != NULL)
		return;

	if (pairingheap_is_empty(&RegisteredSnapshots))
	{
		MyProc->xmin = TransactionXmin = InvalidTransactionId;
		return;
	}

	fc_minSnapshot = pairingheap_container(SnapshotData, ph_node,
										pairingheap_first(&RegisteredSnapshots));

	if (TransactionIdPrecedes(MyProc->xmin, fc_minSnapshot->xmin))
		MyProc->xmin = TransactionXmin = fc_minSnapshot->xmin;
}

/*
 * AtSubCommit_Snapshot
 */
void AtSubCommit_Snapshot(int fc_level)
{
	ActiveSnapshotElt *fc_active;

	/*
	 * 将此子事务中活动快照集重新标记为似乎属于父子事务。
	 */
	for (fc_active = ActiveSnapshot; fc_active != NULL; fc_active = fc_active->as_next)
	{
		if (fc_active->as_level < fc_level)
			break;
		fc_active->as_level = fc_level - 1;
	}
}

/*
 * AtSubAbort_Snapshot
 *		在子事务中止后清理快照
 */
void AtSubAbort_Snapshot(int fc_level)
{
	/* 忘记此子事务设置的活动快照 */
	while (ActiveSnapshot && ActiveSnapshot->as_level >= fc_level)
	{
		ActiveSnapshotElt *fc_next;

		fc_next = ActiveSnapshot->as_next;

		/*
		 * 减少快照的活动计数。如果它仍然被注册或被外部子事务标记为活动状态，我们不能释放它。
		 */
		Assert(ActiveSnapshot->as_snap->active_count >= 1);
		ActiveSnapshot->as_snap->active_count -= 1;

		if (ActiveSnapshot->as_snap->active_count == 0 &&
			ActiveSnapshot->as_snap->regd_count == 0)
			fc_FreeSnapshot(ActiveSnapshot->as_snap);

		/* 并释放堆栈元素 */
		pfree(ActiveSnapshot);

		ActiveSnapshot = fc_next;
		if (ActiveSnapshot == NULL)
			OldestActiveSnapshot = NULL;
	}

	fc_SnapshotResetXmin();
}

/*
 * AtEOXact_Snapshot
 *		快照管理器的清理函数，处理事务结束
 */
void AtEOXact_Snapshot(bool fc_isCommit, bool fc_resetXmin)
{
	/*
	 * 在事务快照模式中，我们必须释放我们私自管理的对事务快照的引用。
	 * 我们必须将其从 RegisteredSnapshots 中移除，以保持下面的检查合理。不过我们不费心去做 FreeSnapshot，原因有两个：内存无论如何会随 TopTransactionContext 消失，如果有人将快照保留为活动状态，我们不希望下面的代码去追逐一个悬挂指针。
	 */
	if (FirstXactSnapshot != NULL)
	{
		Assert(FirstXactSnapshot->regd_count > 0);
		Assert(!pairingheap_is_empty(&RegisteredSnapshots));
		pairingheap_remove(&RegisteredSnapshots, &FirstXactSnapshot->ph_node);
	}
	FirstXactSnapshot = NULL;

	/*
	 * 如果我们导出了任何快照，请进行清理。
	 */
	if (exportedSnapshots != NIL)
	{
		ListCell   *fc_lc;

		/*
		 * 清除文件。失败的 unlink 仅仅是一个警告，因为 (1)
		 * 现在已经太晚去中止事务，(2) 留下一个泄漏的文件几乎没有实际后果。
		 *
		 * 我们还需要从 RegisteredSnapshots 中移除快照，以防止下面的警告。
		 *
		 * 与 FirstXactSnapshot 一样，我们不需要释放快照本身的资源，因为它将随内存上下文一起消失。
		 */
		foreach(fc_lc, exportedSnapshots)
		{
			ExportedSnapshot *fc_esnap = (ExportedSnapshot *) lfirst(fc_lc);

			if (unlink(fc_esnap->snapfile))
				elog(WARNING, "could not unlink file \"%s\": %m",
					 fc_esnap->snapfile);

			pairingheap_remove(&RegisteredSnapshots,
							   &fc_esnap->snapshot->ph_node);
		}

		exportedSnapshots = NIL;
	}

	/* 如果有的话，删除目录快照 */
	InvalidateCatalogSnapshot();

	/* 提交时，抱怨剩余快照 */
	if (fc_isCommit)
	{
		ActiveSnapshotElt *fc_active;

		if (!pairingheap_is_empty(&RegisteredSnapshots))
			elog(WARNING, "registered snapshots seem to remain after cleanup");

		/* 抱怨未弹出的活动快照 */
		for (fc_active = ActiveSnapshot; fc_active != NULL; fc_active = fc_active->as_next)
			elog(WARNING, "snapshot %p still active", fc_active);
	}

	/*
	 * 并重置我们的状态。我们无需显式释放内存 --
	 * 它会随 TopTransactionContext 消失。
	 */
	ActiveSnapshot = NULL;
	OldestActiveSnapshot = NULL;
	pairingheap_reset(&RegisteredSnapshots);

	CurrentSnapshot = NULL;
	SecondarySnapshot = NULL;

	FirstSnapshotSet = false;

	/*
	 * 在正常提交处理过程中，我们调用 ProcArrayEndTransaction() 
	 * 来重置 MyProc->xmin。该调用发生在调用 AtEOXact_Snapshot() 之前，因此我们完全不需要在这里触及 xmin。
	 */
	if (fc_resetXmin)
		fc_SnapshotResetXmin();

	Assert(fc_resetXmin || MyProc->xmin == 0);
}


/*
 * ExportSnapshot
 *		将快照导出到文件，以便其他后端可以导入。
 *		返回可以用于导入此快照的令牌（文件名）。
 */
char * ExportSnapshot(Snapshot fc_snapshot)
{
	TransactionId fc_topXid;
	TransactionId *fc_children;
	ExportedSnapshot *fc_esnap;
	int			fc_nchildren;
	int			fc_addTopXid;
	StringInfoData fc_buf;
	FILE	   *fc_f;
	int			fc_i;
	MemoryContext fc_oldcxt;
	char		fc_path[MAXPGPATH];
	char		fc_pathtmp[MAXPGPATH];

	/*
	 * 在这里调用 RequireTransactionBlock 很诱人，因为导出一个之后立刻消失的快照并不是很有用。
	 * 然而，我们没有足够的信息来这样做，因为我们不知道我们是否处于顶层。例如，我们可能在一个 plpgsql 函数内部，该函数将通过 dblink 发起其他事务。
	 * 与其不允许完全合法的用法，不如不进行检查。
	 *
	 * 还要注意，我们对事务的隔离级别没有任何限制；然而，导入者必须检查它们是否是可串行化的。
	 */

	/*
	 * 如果有的话，获取我们的事务 ID，以便包含在快照中。
	 */
	fc_topXid = GetTopTransactionIdIfAny();

	/*
	 * 我们不能从子事务导出快照，因为没有简单的方法让导入者确认相同的子事务仍在运行。
	 */
	if (IsSubTransaction())
		ereport(ERROR,
				(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
				 errmsg("cannot export a snapshot from a subtransaction")));

	/*
	 * 然而，我们允许已提交的先前子事务存在。
	 * 快照的导入者必须将它们视为仍在运行，因此获取它们的 XID 以将其添加到快照中。
	 */
	fc_nchildren = xactGetCommittedChildren(&fc_children);

	/*
	 * 为快照生成文件路径。我们在事务内部将快照编号从 1 开始。
	 */
	snprintf(fc_path, sizeof(fc_path), SNAPSHOT_EXPORT_DIR "/%08X-%08X-%d",
			 MyProc->backendId, MyProc->lxid, list_length(exportedSnapshots) + 1);

	/*
	 * 将快照复制到 TopTransactionContext，将其添加到
	 * exportedSnapshots 列表中，并标记为伪注册。我们这样做是为了
	 * 确保快照的 xmin 在整个事务过程中得到尊重。
	 */
	fc_snapshot = fc_CopySnapshot(fc_snapshot);

	fc_oldcxt = MemoryContextSwitchTo(TopTransactionContext);
	fc_esnap = (ExportedSnapshot *) palloc(sizeof(ExportedSnapshot));
	fc_esnap->snapfile = pstrdup(fc_path);
	fc_esnap->snapshot = fc_snapshot;
	exportedSnapshots = lappend(exportedSnapshots, fc_esnap);
	MemoryContextSwitchTo(fc_oldcxt);

	fc_snapshot->regd_count++;
	pairingheap_add(&RegisteredSnapshots, &fc_snapshot->ph_node);

	/*
	 * 用文本序列化格式填充 buf，外加关于此事务的标识
	 * 数据。ImportSnapshot 期待的格式相当严格：每行必须是字段名:值。
	 */
	initStringInfo(&fc_buf);

	appendStringInfo(&fc_buf, "vxid:%d/%u\n", MyProc->backendId, MyProc->lxid);
	appendStringInfo(&fc_buf, "pid:%d\n", MyProcPid);
	appendStringInfo(&fc_buf, "dbid:%u\n", MyDatabaseId);
	appendStringInfo(&fc_buf, "iso:%d\n", XactIsoLevel);
	appendStringInfo(&fc_buf, "ro:%d\n", XactReadOnly);

	appendStringInfo(&fc_buf, "xmin:%u\n", fc_snapshot->xmin);
	appendStringInfo(&fc_buf, "xmax:%u\n", fc_snapshot->xmax);

	/*
	 * 我们必须在 top-xid 数据中包含我们自己的顶级事务 ID，因为根据定义，当导入事务采用快照时，我们仍会在运行，但 GetSnapshotData 从不将我们自己的 XID 包含在快照中。
	 * （因此必须有足够的空间添加它。）
	 *
	 * 然而，我们的 topXid 可能在 xmax 之后，在这种情况下
	 * 我们不应包含它，因为 xip[] 成员被期望在 xmax 之前。
	 * （我们无需对 subxip[] 成员进行相同的检查，见 snapshot.h。）
	 */
	fc_addTopXid = (TransactionIdIsValid(fc_topXid) &&
				 TransactionIdPrecedes(fc_topXid, fc_snapshot->xmax)) ? 1 : 0;
	appendStringInfo(&fc_buf, "xcnt:%d\n", fc_snapshot->xcnt + fc_addTopXid);
	for (fc_i = 0; fc_i < fc_snapshot->xcnt; fc_i++)
		appendStringInfo(&fc_buf, "xip:%u\n", fc_snapshot->xip[fc_i]);
	if (fc_addTopXid)
		appendStringInfo(&fc_buf, "xip:%u\n", fc_topXid);

	/*
	 * 类似地，我们将我们已子提交的子 XIDs 添加到 subxid 数据中。在这里，
	 * 我们必须处理可能的溢出问题。
	 */
	if (fc_snapshot->suboverflowed ||
		fc_snapshot->subxcnt + fc_nchildren > GetMaxSnapshotSubxidCount())
		appendStringInfoString(&fc_buf, "sof:1\n");
	else
	{
		appendStringInfoString(&fc_buf, "sof:0\n");
		appendStringInfo(&fc_buf, "sxcnt:%d\n", fc_snapshot->subxcnt + fc_nchildren);
		for (fc_i = 0; fc_i < fc_snapshot->subxcnt; fc_i++)
			appendStringInfo(&fc_buf, "sxp:%u\n", fc_snapshot->subxip[fc_i]);
		for (fc_i = 0; fc_i < fc_nchildren; fc_i++)
			appendStringInfo(&fc_buf, "sxp:%u\n", fc_children[fc_i]);
	}
	appendStringInfo(&fc_buf, "rec:%u\n", fc_snapshot->takenDuringRecovery);

	/*
	 * 现在将文本表示写入文件。我们首先写入 ".tmp" 文件名，若没有错误则重命名为最终文件名。
	 * 这确保没有其他后端可以读取不完整的文件
	 * （ImportSnapshot 不允许这样，因为它的有效字符检查）。
	 */
	snprintf(fc_pathtmp, sizeof(fc_pathtmp), "%s.tmp", fc_path);
	if (!(fc_f = AllocateFile(fc_pathtmp, PG_BINARY_W)))
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not create file \"%s\": %m", fc_pathtmp)));

	if (fwrite(fc_buf.data, fc_buf.len, 1, fc_f) != 1)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not write to file \"%s\": %m", fc_pathtmp)));

	/* 不进行 fsync() 因为文件不需要在系统崩溃后存活 */

	if (FreeFile(fc_f))
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not write to file \"%s\": %m", fc_pathtmp)));

	/*
	 * 现在我们已经将所有内容写入 .tmp 文件，重命名文件以去掉 .tmp 后缀。
	 */
	if (rename(fc_pathtmp, fc_path) < 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not rename file \"%s\" to \"%s\": %m",
						fc_pathtmp, fc_path)));

	
/*
	 * 文件的基本名称是我们从 pg_export_snapshot() 返回的内容。
	 * 它已经以文本格式位于路径中，我们知道路径
	 * 以 SNAPSHOT_EXPORT_DIR 开头。跳过前缀和斜杠，
	 * 并使用 pstrdup 以避免返回局部变量的地址。
	 */
	return pstrdup(fc_path + strlen(SNAPSHOT_EXPORT_DIR) + 1);
}

/*
 * pg_export_snapshot
 *		导出快照的 SQL 可调用封装。
 */
Datum pg_export_snapshot(PG_FUNCTION_ARGS)
{
	char	   *fc_snapshotName;

	fc_snapshotName = ExportSnapshot(GetActiveSnapshot());
	PG_RETURN_TEXT_P(cstring_to_text(fc_snapshotName));
}


/*
 * ImportSnapshot 的解析子例程：解析以给定
 * 前缀开头后跟值的行，并将 *s 向前推进到下一行。
 * 文件名用于错误消息。
 */
static int fc_parseIntFromText(const char *fc_prefix, char **fc_s, const char *fc_filename)
{
	char	   *fc_ptr = *fc_s;
	int			fc_prefixlen = strlen(fc_prefix);
	int			fc_val;

	if (strncmp(fc_ptr, fc_prefix, fc_prefixlen) != 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid snapshot data in file \"%s\"", fc_filename)));
	fc_ptr += fc_prefixlen;
	if (sscanf(fc_ptr, "%d", &fc_val) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid snapshot data in file \"%s\"", fc_filename)));
	fc_ptr = strchr(fc_ptr, '\n');
	if (!fc_ptr)
#ifdef FDD //cppcheck
	{
#endif
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid snapshot data in file \"%s\"", fc_filename)));
#ifdef FDD //cppcheck
		return -1;
	}
#endif
	*fc_s = fc_ptr + 1;
	return fc_val;
}

static TransactionId fc_parseXidFromText(const char *fc_prefix, char **fc_s, const char *fc_filename)
{
	char	   *fc_ptr = *fc_s;
	int			fc_prefixlen = strlen(fc_prefix);
	TransactionId fc_val;

	if (strncmp(fc_ptr, fc_prefix, fc_prefixlen) != 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid snapshot data in file \"%s\"", fc_filename)));
	fc_ptr += fc_prefixlen;
	if (sscanf(fc_ptr, "%u", &fc_val) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid snapshot data in file \"%s\"", fc_filename)));
	fc_ptr = strchr(fc_ptr, '\n');
	if (!fc_ptr)
#ifdef FDD //cppcheck
	{
#endif
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid snapshot data in file \"%s\"", fc_filename)));
#ifdef FDD //cppcheck
		return 0;
	}
#endif
	*fc_s = fc_ptr + 1;
	return fc_val;
}

static void fc_parseVxidFromText(const char *fc_prefix, char **fc_s, const char *fc_filename,
				  VirtualTransactionId *fc_vxid)
{
	char	   *fc_ptr = *fc_s;
	int			fc_prefixlen = strlen(fc_prefix);

	if (strncmp(fc_ptr, fc_prefix, fc_prefixlen) != 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid snapshot data in file \"%s\"", fc_filename)));
	fc_ptr += fc_prefixlen;
	if (sscanf(fc_ptr, "%d/%u", &fc_vxid->backendId, &fc_vxid->localTransactionId) != 2)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid snapshot data in file \"%s\"", fc_filename)));
	fc_ptr = strchr(fc_ptr, '\n');
	if (!fc_ptr)
#ifdef FDD //cppcheck
	{
#endif
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid snapshot data in file \"%s\"", fc_filename)));
#ifdef FDD //cppcheck
		return;
	}
#endif
	*fc_s = fc_ptr + 1;
}

/*
 * ImportSnapshot
 *		导入先前导出的快照。参数应该是一个
 *		SNAPSHOT_EXPORT_DIR 中的文件名。 从该文件加载快照。
 *		这是通过 "SET TRANSACTION SNAPSHOT 'foo'" 调用的。
 */
void ImportSnapshot(const char *fc_idstr)
{
	char		fc_path[MAXPGPATH];
	FILE	   *fc_f;
	struct stat fc_stat_buf;
	char	   *fc_filebuf;
	int			fc_xcnt;
	int			fc_i;
	VirtualTransactionId fc_src_vxid;
	int			fc_src_pid;
	Oid			fc_src_dbid;
	int			fc_src_isolevel;
	bool		fc_src_readonly;
	SnapshotData fc_snapshot;

	/*
	 * 必须在全新事务的顶层。特别注意的是，
	 * 我们检查尚未获取 XID --- 如果获得了，快照可能会显示它未在运行，从而导致非常奇怪的
	 * 行为。
	 */
	if (FirstSnapshotSet ||
		GetTopTransactionIdIfAny() != InvalidTransactionId ||
		IsSubTransaction())
		ereport(ERROR,
				(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
				 errmsg("SET TRANSACTION SNAPSHOT must be called before any query")));

	/*
	 * 如果我们处于读取提交模式，那么下一个查询将使用
	 * 新的快照执行，因此这个函数调用相当于无用。
	 */
	if (!IsolationUsesXactSnapshot())
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("a snapshot-importing transaction must have isolation level SERIALIZABLE or REPEATABLE READ")));

	/*
	 * 验证标识符：仅允许使用 0-9、A-F 和连字符。我们这样做主要是为了防止读取任意文件。
	 */
	if (strspn(fc_idstr, "0123456789ABCDEF-") != strlen(fc_idstr))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid snapshot identifier: \"%s\"", fc_idstr)));

	/* 好的，读取文件 */
	snprintf(fc_path, MAXPGPATH, SNAPSHOT_EXPORT_DIR "/%s", fc_idstr);

	fc_f = AllocateFile(fc_path, PG_BINARY_R);
	if (!fc_f)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid snapshot identifier: \"%s\"", fc_idstr)));

	/* 获取文件大小，以便我们知道需要多少内存 */
	if (fstat(fileno(fc_f), &fc_stat_buf))
		elog(ERROR, "could not stat file \"%s\": %m", fc_path);

	/* 并将文件读取到分配的字符串中 */
	fc_filebuf = (char *) palloc(fc_stat_buf.st_size + 1);
	if (fread(fc_filebuf, fc_stat_buf.st_size, 1, fc_f) != 1)
		elog(ERROR, "could not read file \"%s\": %m", fc_path);

	fc_filebuf[fc_stat_buf.st_size] = '\0';

	FreeFile(fc_f);

	/*
	 * 通过解析文件内容构建快照结构。
	 */
	memset(&fc_snapshot, 0, sizeof(fc_snapshot));

	fc_parseVxidFromText("vxid:", &fc_filebuf, fc_path, &fc_src_vxid);
	fc_src_pid = fc_parseIntFromText("pid:", &fc_filebuf, fc_path);
	/* 我们在这里有些滥用 parseXidFromText ... */
	fc_src_dbid = fc_parseXidFromText("dbid:", &fc_filebuf, fc_path);
	fc_src_isolevel = fc_parseIntFromText("iso:", &fc_filebuf, fc_path);
	fc_src_readonly = fc_parseIntFromText("ro:", &fc_filebuf, fc_path);

	fc_snapshot.snapshot_type = SNAPSHOT_MVCC;

	fc_snapshot.xmin = fc_parseXidFromText("xmin:", &fc_filebuf, fc_path);
	fc_snapshot.xmax = fc_parseXidFromText("xmax:", &fc_filebuf, fc_path);

	fc_snapshot.xcnt = fc_xcnt = fc_parseIntFromText("xcnt:", &fc_filebuf, fc_path);

	/* 在分配内存之前进行 xid 计数的合理性检查 */
	if (fc_xcnt < 0 || fc_xcnt > GetMaxSnapshotXidCount())
#ifdef FDD //cppcheck
	{
#endif
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid snapshot data in file \"%s\"", fc_path)));
#ifdef FDD //cppcheck
		return;
	}
#endif
	fc_snapshot.xip = (TransactionId *) palloc(fc_xcnt * sizeof(TransactionId));
	for (fc_i = 0; fc_i < fc_xcnt; fc_i++)
		fc_snapshot.xip[fc_i] = fc_parseXidFromText("xip:", &fc_filebuf, fc_path);

	fc_snapshot.suboverflowed = fc_parseIntFromText("sof:", &fc_filebuf, fc_path);

	if (!fc_snapshot.suboverflowed)
	{
		fc_snapshot.subxcnt = fc_xcnt = fc_parseIntFromText("sxcnt:", &fc_filebuf, fc_path);

		/* 在分配内存之前进行 xid 计数的合理性检查 */
		if (fc_xcnt < 0 || fc_xcnt > GetMaxSnapshotSubxidCount())
#ifdef FDD //cppcheck
		{
#endif
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
					 errmsg("invalid snapshot data in file \"%s\"", fc_path)));
#ifdef FDD //cppcheck
			return;
		}
#endif
		fc_snapshot.subxip = (TransactionId *) palloc(fc_xcnt * sizeof(TransactionId));
		for (fc_i = 0; fc_i < fc_xcnt; fc_i++)
			fc_snapshot.subxip[fc_i] = fc_parseXidFromText("sxp:", &fc_filebuf, fc_path);
	}
	else
	{
		fc_snapshot.subxcnt = 0;
		fc_snapshot.subxip = NULL;
	}

	fc_snapshot.takenDuringRecovery = fc_parseIntFromText("rec:", &fc_filebuf, fc_path);

	/*
	 * 进行一些额外的合理性检查，仅仅是为了保护自己。我们
	 * 不麻烦去检查数组元素，只检查最关键的
	 * 字段。
	 */
	if (!VirtualTransactionIdIsValid(fc_src_vxid) ||
		!OidIsValid(fc_src_dbid) ||
		!TransactionIdIsNormal(fc_snapshot.xmin) ||
		!TransactionIdIsNormal(fc_snapshot.xmax))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid snapshot data in file \"%s\"", fc_path)));

	/*
	 * 如果我们是可序列化的，则源事务也必须是可序列化的，否则
	 * predicate.c 会出现问题（SxactGlobalXmin 可能向后移动）。此外， 
	 * 非只读事务不能采用只读事务的快照，因为 predicate.c 对这些情况的处理是完全不同的。
	 */
	if (IsolationIsSerializable())
	{
		if (fc_src_isolevel != XACT_SERIALIZABLE)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("a serializable transaction cannot import a snapshot from a non-serializable transaction")));
		if (fc_src_readonly && !XactReadOnly)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("a non-read-only serializable transaction cannot import a snapshot from a read-only transaction")));
	}

	/*
	 * 我们不能导入在不同数据库中拍摄的快照，
	 * 因为 vacuum 根据每个数据库计算 OldestXmin；因此，
	 * 源事务的 xmin 并不能保护我们免受数据丢失。这个
	 * 限制可以被移除，如果源事务将其
	 * xmin 标记为全局适用。但这需要一些
	 * 额外的语法，因为这必须在快照
	 * 最初拍摄时已知。（请参阅 2011-10-21 的 pgsql-hackers 讨论。）
	 */
	if (fc_src_dbid != MyDatabaseId)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot import a snapshot from a different database")));

	/* 好的，安装快照 */
	fc_SetTransactionSnapshot(&fc_snapshot, &fc_src_vxid, fc_src_pid, NULL);
}

/*
 * XactHasExportedSnapshots
 *		测试当前事务是否导出了任何快照。
 */
bool XactHasExportedSnapshots(void)
{
	return (exportedSnapshots != NIL);
}

/*
 * DeleteAllExportedSnapshotFiles
 *		清理任何由崩溃的后端留下的文件
 *		在其终止之前已经导出了快照。
 *
 * 这应该在数据库启动或崩溃恢复期间调用。
 */
void DeleteAllExportedSnapshotFiles(void)
{
	char		fc_buf[MAXPGPATH + sizeof(SNAPSHOT_EXPORT_DIR)];
	DIR		   *fc_s_dir;
	struct dirent *fc_s_de;

	/*
	 * 读取目录或解除文件链接时出现的问题将在
	 * LOG 级别报告。由于我们是在启动过程中运行，ERROR 级别
	 * 将阻止数据库启动，而这并不足以重要。
	 */
	fc_s_dir = AllocateDir(SNAPSHOT_EXPORT_DIR);

	while ((fc_s_de = ReadDirExtended(fc_s_dir, SNAPSHOT_EXPORT_DIR, LOG)) != NULL)
	{
		if (strcmp(fc_s_de->d_name, ".") == 0 ||
			strcmp(fc_s_de->d_name, "..") == 0)
			continue;

		snprintf(fc_buf, sizeof(fc_buf), SNAPSHOT_EXPORT_DIR "/%s", fc_s_de->d_name);

		if (unlink(fc_buf) != 0)
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not remove file \"%s\": %m", fc_buf)));
	}

	FreeDir(fc_s_dir);
}

/*
 * ThereAreNoPriorRegisteredSnapshots
 *		注册的快照计数是否小于或等于一个？
 *
 * 不要使用这个来解决重要决定。虽然零注册和
 * 没有 ActiveSnapshot 会确认某种闲置，但系统对一个注册快照的
 * 重要性并没有保证。
 */
bool ThereAreNoPriorRegisteredSnapshots(void)
{
	if (pairingheap_is_empty(&RegisteredSnapshots) ||
		pairingheap_is_singular(&RegisteredSnapshots))
		return true;

	return false;
}

/*
 * HaveRegisteredOrActiveSnapshots
 *		有没有任何注册或活动快照？
 *
 * 注意：除非被推送或处于活动状态，否则缓存的目录快照不会导致
 * 这个函数返回 true。这使得可以在
 * 强制更长生命周期快照的检查中使用此函数。
 */
bool HaveRegisteredOrActiveSnapshot(void)
{
	if (ActiveSnapshot != NULL)
		return true;

	/*
	 * 在有效时，目录快照在 RegisteredSnapshots 中，但可以
	 * 由于失效处理而随时被移除。如果显式
	 * 注册超过一个快照，必须在 RegisteredSnapshots 中。
	 */
	if (CatalogSnapshot != NULL &&
		pairingheap_is_singular(&RegisteredSnapshots))
		return false;

	return !pairingheap_is_empty(&RegisteredSnapshots);
}


/*
 * 返回一个恰好在分钟边界上的时间戳。
 *
 * 如果参数已经对齐，则返回该值，否则移动到
 * 给定时间后面的下一个分钟边界。
 */
static TimestampTz fc_AlignTimestampToMinuteBoundary(TimestampTz fc_ts)
{
	TimestampTz fc_retval = fc_ts + (USECS_PER_MINUTE - 1);

	return fc_retval - (fc_retval % USECS_PER_MINUTE);
}

/*
 * 获取快照的当前时间戳
 *
 * 这基本上是 GetCurrentTimestamp()，但有保证的
 * 结果永远不会向后移动。
 */
TimestampTz GetSnapshotCurrentTimestamp(void)
{
	TimestampTz fc_now = GetCurrentTimestamp();

	/*
	 * 不要让时间向后移动；如果没有前进，请使用旧值。
	 */
	SpinLockAcquire(&oldSnapshotControl->mutex_current);
	if (fc_now <= oldSnapshotControl->current_timestamp)
		fc_now = oldSnapshotControl->current_timestamp;
	else
		oldSnapshotControl->current_timestamp = fc_now;
	SpinLockRelease(&oldSnapshotControl->mutex_current);

	return fc_now;
}

/*
 * 获取时间戳，基于最后存储的
 * threshold_timestamp 处理的 vacuum。
 *
 * XXX：到目前为止，我们从不相信 64 位值可以被原子性读取；
 * 如果这 ever 改变，我们可以去掉这里的自旋锁。
 */
TimestampTz GetOldSnapshotThresholdTimestamp(void)
{
	TimestampTz fc_threshold_timestamp;

	SpinLockAcquire(&oldSnapshotControl->mutex_threshold);
	fc_threshold_timestamp = oldSnapshotControl->threshold_timestamp;
	SpinLockRelease(&oldSnapshotControl->mutex_threshold);

	return fc_threshold_timestamp;
}

void SetOldSnapshotThresholdTimestamp(TimestampTz fc_ts, TransactionId fc_xlimit)
{
	SpinLockAcquire(&oldSnapshotControl->mutex_threshold);
	Assert(oldSnapshotControl->threshold_timestamp <= fc_ts);
	Assert(TransactionIdPrecedesOrEquals(oldSnapshotControl->threshold_xid, fc_xlimit));
	oldSnapshotControl->threshold_timestamp = fc_ts;
	oldSnapshotControl->threshold_xid = fc_xlimit;
	SpinLockRelease(&oldSnapshotControl->mutex_threshold);
}

/*
 * XXX：魔术以使 old_snapshot_threshold 测试看起来“工作”。它们
 * 目前是破坏性的，关于如何处理它们的讨论
 * 还在进行中。请看
 * https://www.postgresql.org/message-id/20200403001235.e6jfdll3gh2ygbuc%40alap3.anarazel.de
 */
void SnapshotTooOldMagicForTest(void)
{
	TimestampTz fc_ts = GetSnapshotCurrentTimestamp();

	Assert(old_snapshot_threshold == 0);

	fc_ts -= 5 * USECS_PER_SEC;

	SpinLockAcquire(&oldSnapshotControl->mutex_threshold);
	oldSnapshotControl->threshold_timestamp = fc_ts;
	SpinLockRelease(&oldSnapshotControl->mutex_threshold);
}

/*
 * 如果时间戳有有效映射，则将 *xlimitp 设置为
 * 该映射。返回是否存在这样的映射。
 */
static bool fc_GetOldSnapshotFromTimeMapping(TimestampTz fc_ts, TransactionId *fc_xlimitp)
{
	bool		fc_in_mapping = false;

	Assert(fc_ts == fc_AlignTimestampToMinuteBoundary(fc_ts));

	LWLockAcquire(OldSnapshotTimeMapLock, LW_SHARED);

	if (oldSnapshotControl->count_used > 0
		&& fc_ts >= oldSnapshotControl->head_timestamp)
	{
		int			fc_offset;

		fc_offset = ((fc_ts - oldSnapshotControl->head_timestamp)
				  / USECS_PER_MINUTE);
		if (fc_offset > oldSnapshotControl->count_used - 1)
			fc_offset = oldSnapshotControl->count_used - 1;
		fc_offset = (oldSnapshotControl->head_offset + fc_offset)
			% OLD_SNAPSHOT_TIME_MAP_ENTRIES;

		*fc_xlimitp = oldSnapshotControl->xid_by_minute[fc_offset];

		fc_in_mapping = true;
	}

	LWLockRelease(OldSnapshotTimeMapLock);

	return fc_in_mapping;
}

/*
 * TransactionIdLimitedForOldSnapshots
 *
 * 应用旧快照限制。此功能旨在用于页面修剪
 * 和表清理，以允许 old_snapshot_threshold 覆盖正常的
 * 全球 xmin 值。实际测试快照太旧将基于
 * 快照时间戳是否早于在此函数中设置的阈值时间戳。
 *
 * 如果有限的视野允许通常不可能的清理操作，需要在该清理操作之前调用
 * SetOldSnapshotThresholdTimestamp(*limit_ts, *limit_xid)。
 */
bool TransactionIdLimitedForOldSnapshots(TransactionId fc_recentXmin,
									Relation fc_relation,
									TransactionId *fc_limit_xid,
									TimestampTz *fc_limit_ts)
{
	TimestampTz fc_ts;
	TransactionId fc_xlimit = fc_recentXmin;
	TransactionId fc_latest_xmin;
	TimestampTz fc_next_map_update_ts;
	TransactionId fc_threshold_timestamp;
	TransactionId fc_threshold_xid;

	Assert(TransactionIdIsNormal(fc_recentXmin));
	Assert(OldSnapshotThresholdActive());
	Assert(fc_limit_ts != NULL && fc_limit_xid != NULL);

	
/*
	 * TestForOldSnapshot() 假设早期修剪会提前页面 LSN，因此在跳过 WAL 时我们不能进行早期修剪。
	 */
	if (!RelationAllowsEarlyPruning(fc_relation) || !RelationNeedsWAL(fc_relation))
		return false;

	fc_ts = GetSnapshotCurrentTimestamp();

	SpinLockAcquire(&oldSnapshotControl->mutex_latest_xmin);
	fc_latest_xmin = oldSnapshotControl->latest_xmin;
	fc_next_map_update_ts = oldSnapshotControl->next_map_update;
	SpinLockRelease(&oldSnapshotControl->mutex_latest_xmin);

	/*
	 * 零阈值总是覆盖到最新的 xmin（如果有效）。没有一些启发式的方法，它会在例如简单的 UPDATE 上发现自己的快照太旧——这会使其对大多数测试变得毫无用处，但没有任何原则性的方法可以确保它不会以这种方式失败。
	 * 使用五秒延迟以尝试获得有用的测试行为，但这可能需要调整。
	 */
	if (old_snapshot_threshold == 0)
	{
		if (TransactionIdPrecedes(fc_latest_xmin, MyProc->xmin)
			&& TransactionIdFollows(fc_latest_xmin, fc_xlimit))
			fc_xlimit = fc_latest_xmin;

		fc_ts -= 5 * USECS_PER_SEC;
	}
	else
	{
		fc_ts = fc_AlignTimestampToMinuteBoundary(fc_ts)
			- (old_snapshot_threshold * USECS_PER_MINUTE);

		/* 检查无 LW 锁定的快速退出。 */
		SpinLockAcquire(&oldSnapshotControl->mutex_threshold);
		fc_threshold_timestamp = oldSnapshotControl->threshold_timestamp;
		fc_threshold_xid = oldSnapshotControl->threshold_xid;
		SpinLockRelease(&oldSnapshotControl->mutex_threshold);

		if (fc_ts == fc_threshold_timestamp)
		{
			/*
			 * 当前时间戳与最后应用的限制在同一个桶中。重用。
			 */
			fc_xlimit = fc_threshold_xid;
		}
		else if (fc_ts == fc_next_map_update_ts)
		{
			/*
			 * FIXME：这个分支非常不确定 - 但这可能应该单独修复。
			 */
			fc_xlimit = fc_latest_xmin;
		}
		else if (fc_GetOldSnapshotFromTimeMapping(fc_ts, &fc_xlimit))
		{
		}

		/*
		 * 对活动事务的清理工作进行安全保护。
		 *
		 * 这不是一个断言，因为我们为了性能避免使用自旋锁，
		 * 留下了 xlimit 可能会推进并变得更为当前的可能性；但
		 * 应用这个限制似乎是谨慎的。它可能会使修剪的攻击性
		 * 稍微降低，但能防止数据丢失错误。
		 */
		if (TransactionIdIsNormal(fc_latest_xmin)
			&& TransactionIdPrecedes(fc_latest_xmin, fc_xlimit))
			fc_xlimit = fc_latest_xmin;
	}

	if (TransactionIdIsValid(fc_xlimit) &&
		TransactionIdFollowsOrEquals(fc_xlimit, fc_recentXmin))
	{
		*fc_limit_ts = fc_ts;
		*fc_limit_xid = fc_xlimit;

		return true;
	}

	return false;
}

/*
 * 处理将时间映射到 xid 的循环缓冲区。
 */
void MaintainOldSnapshotTimeMapping(TimestampTz fc_whenTaken, TransactionId fc_xmin)
{
	TimestampTz fc_ts;
	TransactionId fc_latest_xmin;
	TimestampTz update_ts;
	bool		fc_map_update_required = false;

	/* 禁止在禁用旧快照检查时调用此函数。 */
	Assert(old_snapshot_threshold >= 0);

	fc_ts = fc_AlignTimestampToMinuteBoundary(fc_whenTaken);

	/*
	 * 跟踪任何进程看到的最新 xmin。当我们跨越一个桶边界时
	 * 用新值更新映射。
	 */
	SpinLockAcquire(&oldSnapshotControl->mutex_latest_xmin);
	fc_latest_xmin = oldSnapshotControl->latest_xmin;
	update_ts = oldSnapshotControl->next_map_update;
	if (fc_ts > update_ts)
	{
		oldSnapshotControl->next_map_update = fc_ts;
		fc_map_update_required = true;
	}
	if (TransactionIdFollows(fc_xmin, fc_latest_xmin))
		oldSnapshotControl->latest_xmin = fc_xmin;
	SpinLockRelease(&oldSnapshotControl->mutex_latest_xmin);

	/* 我们只需更新最近的 xmin 值。 */
	if (!fc_map_update_required)
		return;

	/* 对于 0（用于测试）无需进一步跟踪。 */
	if (old_snapshot_threshold == 0)
		return;

	/*
	 * 我们不想对不寻常的值做出愚蠢的操作，但我们不想
	 * 在日志中堆积警告或破坏这个特性的正常处理；
	 * 所以如果有什么看起来不合理，就以 DEBUG 级别记录并
	 * 返回而不做任何事情。
	 */
	if (fc_whenTaken < 0)
	{
		elog(DEBUG1,
			 "MaintainOldSnapshotTimeMapping called with negative whenTaken = %ld",
			 (long) fc_whenTaken);
		return;
	}
	if (!TransactionIdIsNormal(fc_xmin))
	{
		elog(DEBUG1,
			 "MaintainOldSnapshotTimeMapping called with xmin = %lu",
			 (unsigned long) fc_xmin);
		return;
	}

	LWLockAcquire(OldSnapshotTimeMapLock, LW_EXCLUSIVE);

	Assert(oldSnapshotControl->head_offset >= 0);
	Assert(oldSnapshotControl->head_offset < OLD_SNAPSHOT_TIME_MAP_ENTRIES);
	Assert((oldSnapshotControl->head_timestamp % USECS_PER_MINUTE) == 0);
	Assert(oldSnapshotControl->count_used >= 0);
	Assert(oldSnapshotControl->count_used <= OLD_SNAPSHOT_TIME_MAP_ENTRIES);

	if (oldSnapshotControl->count_used == 0)
	{
		/* 设置空映射的第一个条目 */
		oldSnapshotControl->head_offset = 0;
		oldSnapshotControl->head_timestamp = fc_ts;
		oldSnapshotControl->count_used = 1;
		oldSnapshotControl->xid_by_minute[0] = fc_xmin;
	}
	else if (fc_ts < oldSnapshotControl->head_timestamp)
	{
		/* 旧的时间戳；以 DEBUG 级别记录 */
		LWLockRelease(OldSnapshotTimeMapLock);
		elog(DEBUG1,
			 "MaintainOldSnapshotTimeMapping called with old whenTaken = %ld",
			 (long) fc_whenTaken);
		return;
	}
	else if (fc_ts <= (oldSnapshotControl->head_timestamp +
					((oldSnapshotControl->count_used - 1)
					 * USECS_PER_MINUTE)))
	{
		/* 已存在的映射；如果可能则推进 xid */
		int			fc_bucket = (oldSnapshotControl->head_offset
							  + ((fc_ts - oldSnapshotControl->head_timestamp)
								 / USECS_PER_MINUTE))
		% OLD_SNAPSHOT_TIME_MAP_ENTRIES;

		if (TransactionIdPrecedes(oldSnapshotControl->xid_by_minute[fc_bucket], fc_xmin))
			oldSnapshotControl->xid_by_minute[fc_bucket] = fc_xmin;
	}
	else
	{
		/* 我们需要一个新桶，但它可能不是下一个。 */
		int			fc_distance_to_new_tail;
		int			fc_distance_to_current_tail;
		int			fc_advance;

		/*
		 * 我们的目标是使映射的新“尾部”，即最新且距
		 * “头”条目最远的条目，对应于“ts”。由于每分钟
		 * 有一个条目，当前头部和新尾部之间的距离就是 ts
		 * 和当前 head_timestamp 之间的分钟差。
		 *
		 * 当前头到当前尾的距离比映射中的条目数少一，
		 * 因为头_offset 处的条目是 head_timestamp 后 0
		 * 分钟的条目。
		 *
		 * 这两个值之间的差异是我们需要推动映射的分钟数，
		 * 可以通过添加新条目或旋转旧条目来实现。
		 */
		fc_distance_to_new_tail =
			(fc_ts - oldSnapshotControl->head_timestamp) / USECS_PER_MINUTE;
		fc_distance_to_current_tail =
			oldSnapshotControl->count_used - 1;
		fc_advance = fc_distance_to_new_tail - fc_distance_to_current_tail;
		Assert(fc_advance > 0);

		if (fc_advance >= OLD_SNAPSHOT_TIME_MAP_ENTRIES)
		{
			/* 推进的距离已经使所有旧数据无效；重新开始。 */
			oldSnapshotControl->head_offset = 0;
			oldSnapshotControl->count_used = 1;
			oldSnapshotControl->xid_by_minute[0] = fc_xmin;
			oldSnapshotControl->head_timestamp = fc_ts;
		}
		else
		{
			/* 将新值存储在一个或多个桶中。 */
			int			fc_i;

			for (fc_i = 0; fc_i < fc_advance; fc_i++)
			{
				if (oldSnapshotControl->count_used == OLD_SNAPSHOT_TIME_MAP_ENTRIES)
				{
					/* 映射满了，新值替换旧头部。 */
					int			fc_old_head = oldSnapshotControl->head_offset;

					if (fc_old_head == (OLD_SNAPSHOT_TIME_MAP_ENTRIES - 1))
						oldSnapshotControl->head_offset = 0;
					else
						oldSnapshotControl->head_offset = fc_old_head + 1;
					oldSnapshotControl->xid_by_minute[fc_old_head] = fc_xmin;
					oldSnapshotControl->head_timestamp += USECS_PER_MINUTE;
				}
				else
				{
					/* 扩展映射至未使用的条目。 */
					int			fc_new_tail = (oldSnapshotControl->head_offset
											+ oldSnapshotControl->count_used)
					% OLD_SNAPSHOT_TIME_MAP_ENTRIES;

					oldSnapshotControl->count_used++;
					oldSnapshotControl->xid_by_minute[fc_new_tail] = fc_xmin;
				}
			}
		}
	}

	LWLockRelease(OldSnapshotTimeMapLock);
}


/*
 * 设置一个快照，替换正常的目录快照，使目录访问
 * 的行为就像在过去的某个时刻一样。
 *
 * 逻辑解码所需。
 */
void SetupHistoricSnapshot(Snapshot fc_historic_snapshot, HTAB *fc_tuplecids)
{
	Assert(fc_historic_snapshot != NULL);

	/* 设置时空快照 */
	HistoricSnapshot = fc_historic_snapshot;

	/* 设置 (cmin, cmax) 查找哈希 */
	tuplecid_data = fc_tuplecids;
}


/*
 * 使目录快照恢复正常行为。
 */
void TeardownHistoricSnapshot(bool fc_is_error)
{
	HistoricSnapshot = NULL;
	tuplecid_data = NULL;
}

bool HistoricSnapshotActive(void)
{
	return HistoricSnapshot != NULL;
}

HTAB * HistoricSnapshotGetTupleCids(void)
{
	Assert(HistoricSnapshotActive());
	return tuplecid_data;
}

/*
 * EstimateSnapshotSpace
 *		返回存储给定快照所需的大小。
 *
 * 我们只导出了存储在 SerializedSnapshotData 中的快照
 * 所需的字段。
 */
Size EstimateSnapshotSpace(Snapshot fc_snap)
{
	Size		fc_size;

	Assert(fc_snap != InvalidSnapshot);
	Assert(fc_snap->snapshot_type == SNAPSHOT_MVCC);

	/* 我们在同一个 palloc 块中分配所需的任何 XID 数组。 */
	fc_size = add_size(sizeof(SerializedSnapshotData),
					mul_size(fc_snap->xcnt, sizeof(TransactionId)));
	if (fc_snap->subxcnt > 0 &&
		(!fc_snap->suboverflowed || fc_snap->takenDuringRecovery))
		fc_size = add_size(fc_size,
						mul_size(fc_snap->subxcnt, sizeof(TransactionId)));

	return fc_size;
}

/*
 * SerializeSnapshot
 *		将序列化快照（从给定快照中提取）转存到
 *		start_address 的内存位置。
 */
void SerializeSnapshot(Snapshot fc_snapshot, char *fc_start_address)
{
	SerializedSnapshotData fc_serialized_snapshot;

	Assert(fc_snapshot->subxcnt >= 0);

	/* 复制所有必需字段 */
	fc_serialized_snapshot.xmin = fc_snapshot->xmin;
	fc_serialized_snapshot.xmax = fc_snapshot->xmax;
	fc_serialized_snapshot.xcnt = fc_snapshot->xcnt;
	fc_serialized_snapshot.subxcnt = fc_snapshot->subxcnt;
	fc_serialized_snapshot.suboverflowed = fc_snapshot->suboverflowed;
	fc_serialized_snapshot.takenDuringRecovery = fc_snapshot->takenDuringRecovery;
	fc_serialized_snapshot.curcid = fc_snapshot->curcid;
	fc_serialized_snapshot.whenTaken = fc_snapshot->whenTaken;
	fc_serialized_snapshot.lsn = fc_snapshot->lsn;

	/*
	 * 如果 SubXID 数组溢出则忽略它，除非快照是在
	 * 恢复期间拍摄的——在这种情况下，顶级 XIDs 也在
	 * subxip 中，我们必须保留它们。
	 */
	if (fc_serialized_snapshot.suboverflowed && !fc_snapshot->takenDuringRecovery)
		fc_serialized_snapshot.subxcnt = 0;

	/* 将结构复制到可能未对齐的缓冲区 */
	memcpy(fc_start_address,
		   &fc_serialized_snapshot, sizeof(SerializedSnapshotData));

	/* 复制 XID 数组 */
	if (fc_snapshot->xcnt > 0)
		memcpy((TransactionId *) (fc_start_address +
								  sizeof(SerializedSnapshotData)),
			   fc_snapshot->xip, fc_snapshot->xcnt * sizeof(TransactionId));

	/*
	 * 复制 SubXID 数组。如果它已溢出则无须复制，
	 * 因为在这种情况下它没有被使用。除非是在恢复期间拍摄的
	 * 快照；因为在这种情况下，所有顶级 XIDs 也在 subxip 中，
	 * 所以我们必须保留它们。
	 */
	if (fc_serialized_snapshot.subxcnt > 0)
	{
		Size		fc_subxipoff = sizeof(SerializedSnapshotData) +
		fc_snapshot->xcnt * sizeof(TransactionId);

		memcpy((TransactionId *) (fc_start_address + fc_subxipoff),
			   fc_snapshot->subxip, fc_snapshot->subxcnt * sizeof(TransactionId));
	}
}

/*
 * RestoreSnapshot
 *		从指定地址恢复序列化快照。
 *
 * 复制是在 TopTransactionContext 中 palloc 的，初始
 * 引用计数设置为 0。返回的快照已设置复制标志。
 */
Snapshot RestoreSnapshot(char *fc_start_address)
{
	SerializedSnapshotData fc_serialized_snapshot;
	Size		fc_size;
	Snapshot	fc_snapshot;
	TransactionId *fc_serialized_xids;

	memcpy(&fc_serialized_snapshot, fc_start_address,
		   sizeof(SerializedSnapshotData));
	fc_serialized_xids = (TransactionId *)
		(fc_start_address + sizeof(SerializedSnapshotData));

	/* 我们在同一个 palloc 块中分配所需的任何 XID 数组。 */
	fc_size = sizeof(SnapshotData)
		+ fc_serialized_snapshot.xcnt * sizeof(TransactionId)
		+ fc_serialized_snapshot.subxcnt * sizeof(TransactionId);

	/* 复制所有必需字段 */
	fc_snapshot = (Snapshot) MemoryContextAlloc(TopTransactionContext, fc_size);
	fc_snapshot->snapshot_type = SNAPSHOT_MVCC;
	fc_snapshot->xmin = fc_serialized_snapshot.xmin;
	fc_snapshot->xmax = fc_serialized_snapshot.xmax;
	fc_snapshot->xip = NULL;
	fc_snapshot->xcnt = fc_serialized_snapshot.xcnt;
	fc_snapshot->subxip = NULL;
	fc_snapshot->subxcnt = fc_serialized_snapshot.subxcnt;
	fc_snapshot->suboverflowed = fc_serialized_snapshot.suboverflowed;
	fc_snapshot->takenDuringRecovery = fc_serialized_snapshot.takenDuringRecovery;
	fc_snapshot->curcid = fc_serialized_snapshot.curcid;
	fc_snapshot->whenTaken = fc_serialized_snapshot.whenTaken;
	fc_snapshot->lsn = fc_serialized_snapshot.lsn;
	fc_snapshot->snapXactCompletionCount = 0;

	/* 复制 XIDs（如果存在）。 */
	if (fc_serialized_snapshot.xcnt > 0)
	{
		fc_snapshot->xip = (TransactionId *) (fc_snapshot + 1);
		memcpy(fc_snapshot->xip, fc_serialized_xids,
			   fc_serialized_snapshot.xcnt * sizeof(TransactionId));
	}

	/* 复制 SubXIDs（如果存在）。 */
	if (fc_serialized_snapshot.subxcnt > 0)
	{
		fc_snapshot->subxip = ((TransactionId *) (fc_snapshot + 1)) +
			fc_serialized_snapshot.xcnt;
		memcpy(fc_snapshot->subxip, fc_serialized_xids + fc_serialized_snapshot.xcnt,
			   fc_serialized_snapshot.subxcnt * sizeof(TransactionId));
	}

	/* 设置已复制标志，以便调用者可以正确设置引用计数。 */
	fc_snapshot->regd_count = 0;
	fc_snapshot->active_count = 0;
	fc_snapshot->copied = true;

	return fc_snapshot;
}

/*
 * 将恢复的快照安装为事务快照。
 *
 * 第二个参数为 void * 类型，因此 snapmgr.h 不需要包含
 * PGPROC 的声明。
 */
void RestoreTransactionSnapshot(Snapshot fc_snapshot, void *fc_source_pgproc)
{
	fc_SetTransactionSnapshot(fc_snapshot, NULL, InvalidPid, fc_source_pgproc);
}

/*
 * XidInMVCCSnapshot
 *		给定的 XID 在快照中是否仍在进行中？
 *
 * 注意：GetSnapshotData 从不将我们自己的后端的顶级 xid 或子 xid 存储到快照中，
 * 所以这些 xid 不会被该函数报告为“运行中”。对于当前的用途这是可以的，
 * 因为我们总是先检查 TransactionIdIsCurrentTransactionId，除非已知
 * XID 不可能是我们的。
 */
bool XidInMVCCSnapshot(TransactionId fc_xid, Snapshot fc_snapshot)
{
	uint32		fc_i;

	/*
	 * 进行快速范围检查以消除大多数 XID 而无需查看
	 * xip 数组。请注意，即使我们在下面将子事务 XID 转换为
	 * 其父级，这也是可以的，因为一个 XID < xmin 的子事务
	 * 也肯定有一个 XID < xmin 的父事务，而一个 XID >= xmax
	 * 的必须属于在此快照时尚未提交的父事务。
	 */

	/* 任何 xid < xmin 的都不是进行中的 */
	if (TransactionIdPrecedes(fc_xid, fc_snapshot->xmin))
		return false;
	/* 任何 xid >= xmax 的都是进行中的 */
	if (TransactionIdFollowsOrEquals(fc_xid, fc_snapshot->xmax))
		return true;

	/*
	 * 快照信息在恢复期间拍摄的快照中存储略有不同。
	 */
	if (!fc_snapshot->takenDuringRecovery)
	{
		/*
		 * 如果快照包含完整的子事务数据，检查事物的最快方法
		 * 是将给定的 XID 与子事务 XID 和顶级 XID 进行比较。
		 * 如果快照溢出，我们必须使用 pg_subtrans 将子事务 XID 转换为其父级 XID，
		 * 但随后我们只需查看顶级 XID 而不是子事务。
		 */
		if (!fc_snapshot->suboverflowed)
		{
			/* 我们有完整的数据，因此搜索 subxip */
			int32		fc_j;

			for (fc_j = 0; fc_j < fc_snapshot->subxcnt; fc_j++)
			{
				if (TransactionIdEquals(fc_xid, fc_snapshot->subxip[fc_j]))
					return true;
			}

			/* 不在那儿，继续搜索 xip[] */
		}
		else
		{
			/*
			 * 快照溢出，因此将 xid 转换为顶级。这是安全的
			 * 因为我们在上面消除了过旧的 XID。
			 */
			fc_xid = SubTransGetTopmostTransaction(fc_xid);

			/*
			 * 如果 xid 确实是子事务，我们现在可能有一个 xid < xmin，
			 * 因此重新检查以避免数组扫描。没有必要重新检查
			 * xmax。
			 */
			if (TransactionIdPrecedes(fc_xid, fc_snapshot->xmin))
				return false;
		}

		for (fc_i = 0; fc_i < fc_snapshot->xcnt; fc_i++)
		{
			if (TransactionIdEquals(fc_xid, fc_snapshot->xip[fc_i]))
				return true;
		}
	}
	else
	{
		int32		fc_j;

		/*
		 * 在恢复期间，我们将所有 xid 存储在子事务数组中，因为它
		 * 远远是更大的数组，而我们大多数情况下不知道哪些 xid 是
		 * 顶级的，哪些是子事务。xip 数组是空的。
		 *
		 * 如果我们溢出，我们将从搜索 subtrans 开始。
		 */
		if (fc_snapshot->suboverflowed)
		{
			/*
			 * 快照溢出，因此将 xid 转换为顶级。这是安全的
			 * 因为我们在上面消除了过旧的 XID。
			 */
			fc_xid = SubTransGetTopmostTransaction(fc_xid);

			/*
			 * 如果 xid 确实是子事务，我们现在可能有一个 xid < xmin，
			 * 因此重新检查以避免数组扫描。没有必要重新检查
			 * xmax。
			 */
			if (TransactionIdPrecedes(fc_xid, fc_snapshot->xmin))
				return false;
		}

		/*
		 * 我们现在要么有一个高于 xmin 的顶级 xid，要么有一个
		 * 不确定的 xid。我们不知道它是顶级还是子事务，
		 * 但这无所谓。如果它存在，则该 xid 是可见的。
		 */
		for (fc_j = 0; fc_j < fc_snapshot->subxcnt; fc_j++)
		{
			if (TransactionIdEquals(fc_xid, fc_snapshot->subxip[fc_j]))
				return true;
		}
	}

	return false;
}
